﻿using System;
using System.ComponentModel;
using System.Runtime.Serialization;

using SharpGL.SceneGraph.Effects;

namespace SharpGL.SceneGraph
{
    [DataContract()]
    public abstract class ContextualSceneElement : SceneElement, IHasOpenGLContext, IDisposable
    {
        #region Private Consts

        private static readonly OpenGL defaultCurrentOpenGLContext = null;

        #endregion Private Consts
        
        #region Private Fields

        /// <summary>
        /// Current OpenGL context
        /// </summary>
        [IgnoreDataMember()]
        private OpenGL currentOpenGLContext;

        #endregion Private Fields

        #region Protected Methods

        /// <summary>
        /// Called when creating in the OpenGL instance.
        /// </summary>
        /// <param name="gl">The OpenGL instance.</param>
        protected abstract void OnCreateInContext(OpenGL gl);

        protected override void OnDeserializing()
        {
            base.OnDeserializing();

            currentOpenGLContext = defaultCurrentOpenGLContext;
        }
        
        /// <summary>
        /// Called when destroy in the OpenGL instance.
        /// </summary>
        protected abstract void OnDestroyInContext(OpenGL gl);

        public void ValidateCreationContext(OpenGL gl)
        {
            if (!OpenGL.Equals(gl, currentOpenGLContext))
            {
                if (OpenGL.IsValidContext(currentOpenGLContext))
                    throw new Exception("Cannot create '" +
                                        Name +
                                        "' in context '" +
                                        (OpenGL.IsValidContext(gl) ? gl.Renderer : "Null") +
                                        "' - it is already created in '" +
                                        (OpenGL.IsValidContext(currentOpenGLContext) ? currentOpenGLContext.Renderer : "Null") +
                                        "' OpenGL context.");

                if (!OpenGL.IsValidContext(gl))
                    throw new Exception("Cannot create '" +
                                        Name + 
                                        "' in illegal OpenGL context.");
            }
        }
        
        public void ValidateRenderingContext(OpenGL gl)
        {
            if (!OpenGL.Equals(gl, currentOpenGLContext))
                throw new Exception("Cannot render '" +
                                    Name +
                                    "' in context '" +
                                    (OpenGL.IsValidContext(gl) ? gl.Renderer : "Null") +
                                    "' - it is created in '" +
                                    (OpenGL.IsValidContext(currentOpenGLContext) ? currentOpenGLContext.Renderer : "Null") +
                                    "' OpenGL context.");

            if (!OpenGL.IsValidContext(gl))
                throw new Exception("Cannot render '" +
                                    Name +
                                    "' in illegal OpenGL context.");
        }

        #endregion Protected Methods

        #region Public Constructors/Destructor

        /// <summary>
        /// Initializes a new instance of the <see cref="ContextualSceneElement"/> class.
        /// </summary>
        public ContextualSceneElement()
        {
            currentOpenGLContext = defaultCurrentOpenGLContext;
        }

        ~ContextualSceneElement()
        {
            DestroyInContext();
        }

        #endregion Public Constructors/Destructor

        #region Public Methods

        /// <summary>
        /// Create in the context of the supplied OpenGL instance.
        /// </summary>
        /// <param name="gl">The OpenGL instance.</param>
        public virtual void CreateInContext(OpenGL gl)
        {
            ValidateCreationContext(gl);
            
            // Set current OpenGL context.
            currentOpenGLContext = gl;

            // Create this scene element.
            OnCreateInContext(gl);

            // Raise context changed event.
            NotifyContextChanged(this, new ContextChangeEventArgs(ContextChangeAction.CreateInContext));
        }

        /// <summary>
        /// Destroy in the current OpenGL instance.
        /// </summary>
        public virtual void DestroyInContext()
        {
            if (OpenGL.IsValidContext(currentOpenGLContext))
            {
                // Raise context changed event.
                NotifyContextChanged(this, new ContextChangeEventArgs(ContextChangeAction.DestroyInContext));

                // Destroy this scene element.
                OnDestroyInContext(currentOpenGLContext);

                currentOpenGLContext = null;
            }
        }

        public void Dispose()
        {
            DestroyInContext();

            GC.SuppressFinalize(this);
        }

		#endregion

        #region Public Properties

        /// <summary>
        /// Current OpenGL context
        /// </summary>
        [Browsable(false)]
        public OpenGL CurrentOpenGLContext
        {
            get { return currentOpenGLContext; }
            private set { currentOpenGLContext = value; }
        }

        #endregion Public Properties
    }
}
